
/*
 * GLStuff.d: OpenGL support.
 * Author: Guilherme R. Lampert
 * 2013-11-27
 */

import std.stdio;
import std.file;
import derelict.opengl3.gl;
import derelict.glfw3.glfw3;

// Fixed window sizes:
enum WINDOW_WIDTH = ((10 * 2) * 32) + 15 + 30; // Size of 2 inventories, side by side, plus a 15px gap between them
enum WINDOW_HEIGHT = (20 * 32) + 30;

// Global window instance:
GLFWwindow * glWindow = null;

// Setting this to true disabled OpenGL for the whole demo.
bool noGL = false;

// ======================================
// GLStartup():
// ======================================

bool GLStartup(string title)
{
	DerelictGLFW3.load();
	if (!glfwInit())
	{
		writeln("ERROR: Failed to init GLFW!");
		return (false);
	}

	/*
	// This enables core 3+ profile, but we won't use it this time.
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
	*/

	// No window resize!
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

	// Create a windowed mode window and its OpenGL context:
	glWindow = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, std.string.toStringz(title), null, null);
	if (!glWindow)
	{
		writeln("ERROR: Failed to create GLFW window!");
		glfwTerminate();
		return (false);
	}

	// Make the window's context current:
	glfwMakeContextCurrent(glWindow);

	DerelictGL.load();
	writeln("GL_VERSION:  ", std.conv.to!string(glGetString(GL_VERSION)));
	writeln("GL_RENDERER: ", std.conv.to!string(glGetString(GL_RENDERER)));
	writeln("GL_VENDOR:   ", std.conv.to!string(glGetString(GL_VENDOR)));

	glClearColor(0.25f, 0.1f, 0.0f, 1.0f);
	int width, height;
	glfwGetFramebufferSize(glWindow, &width, &height);
	glViewport(0, 0, width, height);
	writeln("Framebuffer size: ", width, "x", height);

	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

	// Set 2D Projection:
	glOrtho(0.0, cast(double)WINDOW_WIDTH, cast(double)WINDOW_HEIGHT, 0.0, -1.0, 1.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glEnable(GL_TEXTURE_2D);
	glDisable(GL_DEPTH_TEST); // Only 2D drawings

	// Clear error flag:
	glGetError();

	return (true);
}

// ======================================
// GLShutdown():
// ======================================

void GLShutdown()
{
	glfwDestroyWindow(glWindow);
	glWindow = null;
	glfwTerminate();
}

// ======================================
// Image helpers:
// ======================================

// Static non-animated image and GL texture id:
class Image {
	uint width;       // Width in pixels
	uint height;      // Height in pixels
	uint colorComps;  // Num color components
	ubyte[] pixels;   // Pixel data
	GLuint textureId; // Texture id
};

// Loads 24bit and 32bit uncompressed TGA images from file.
Image LoadImageFromFile(string filename)
{
	Image img = new Image();
	ubyte[] bytes = cast(ubyte[])read(filename); // Read entire file into memory

	// Seek to the width (byte 12 & 13):
	const short w = ((bytes[13] << 8) | bytes[12]);

	// Seek to the height (byte 14 & 15):
	const short h = ((bytes[15] << 8) | bytes[14]);

	// Seek to the depth (byte 16):
	const ubyte depth = bytes[16];

	// Copy image data (byte 18 and on):
	img.pixels = bytes[18 .. $];

	// Debugging:
	debug writeln("Loaded TGA image: ", filename, " => ", w, "x", h, ", depth: ", depth, ", size: ", (h * w * (depth / 8)), " bytes");

	// Fill the Image info:
	img.width  = w;
	img.height = h;
	img.colorComps = (depth / 8);

	GLenum internalFormat, format, type;
	if (img.colorComps == 4)
	{
		internalFormat = GL_RGBA;
		format = GL_BGRA;
		type = GL_UNSIGNED_BYTE;
	}
	else if (img.colorComps == 3)
	{
		internalFormat = GL_RGB;
		format = GL_BGR;
		type = GL_UNSIGNED_BYTE;
	}
	else
	{
		assert(0); // FIXME: Only BGR and BGRA for now!
	}

	// Create texture:
	glGenTextures(1, &img.textureId);
	glBindTexture(GL_TEXTURE_2D, img.textureId);
	glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, img.width, img.height, 0, format, GL_UNSIGNED_BYTE, img.pixels.ptr);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL,  0);
	glBindTexture(GL_TEXTURE_2D, 0);

	if (glGetError() == GL_NO_ERROR)
	{
		writeln("GL texture created with no errors!");
	}
	else
	{
		writeln("WARNING! GL texture creation generated errors! ...");
	}

	return (img);
}

// This function expects the proper GL states to be set, such as an ortho 2D projection, color enabled, etc...
void DrawImage(uint x, uint y, uint w, uint h, ref const Image img)
{
	x += 15;
	y += 15;

	glBindTexture(GL_TEXTURE_2D, img.textureId);

	glBegin(GL_QUADS);

	glTexCoord2f(0.0f, 0.0f);
	glVertex2i(x, y);

	glTexCoord2f(1.0f, 0.0f);
	glVertex2i(x + w, y);

	glTexCoord2f(1.0f, 1.0f);
	glVertex2i(x + w, y + h);

	glTexCoord2f(0.0f, 1.0f);
	glVertex2i(x, y + h);

	glEnd();
}
